package br.usp.poli.compilador2008.syntatic;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Stack;
import java.lang.Integer;
import java.lang.reflect.InvocationTargetException;
import org.apache.log4j.*;

import br.usp.poli.compilador2008.lexical.Lexical;
import br.usp.poli.compilador2008.lexical.Token;
import br.usp.poli.compilador2008.lexical.TokenSet;
import br.usp.poli.compilador2008.lexical.TokenType;
import br.usp.poli.compilador2008.syntatic.Transition;
import br.usp.poli.compilador2008.syntatic.Configuration;

//TODO store all the syntatic analysis in a File and add the syntatic functionality to the APP GUI.

public class Syntatic {	
	public Logger synLog;
	
	private File source;
	private Lexical lex;
	private Token currentToken;
	//private Semantico sem;	

	private int currentState;
	private SubMachine currentSubMachine;	
	private static Stack<Configuration> configStack;


	/**
	 * SYNTATIC (analyser) 
	 * 
	 * This object needs the implementation of a lexical analyser, and a semantic analyser 
	 * (which will provide the functions to be used from inside the syntatic analyser). This
	 * lexic analyser must implement a tokenSet, which will manage all the read tokens.
	 * 
	 * The Syntatic analyser contains a list of all submachines, and each submachine has all the
	 * possible transitions it supports. The transitions are populated in the 
	 * listPossibleTransitions function. 
	 * 
	 * The main method consists of reading all the tokens from the lexical analysis, and for each token
	 * deciding what to. First, it checks if the token is of the kind "END OF FILE". If it is, it checks
	 * if the current state is a final one. 
	 * 
	 * There is a submachineStack, responsible to store all the current machines. When a submachine is called, 
	 * the old submachine is stored and when the new submachine stops, the old submachine is restored.
	 * 
	 * 
	 * 
	 * @param source
	 */
	public Syntatic(File source){
		this(source.getAbsolutePath());
	}	

	public Syntatic(String filePath){
		this(new Lexical(filePath));
	}

	public Syntatic(Lexical _lex)
	{
		this.lex = _lex;	//set the Lexical analyser
		try {
			this.lex.populateTokenSet();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		//sets the initial values for the PR submachine
		this.currentSubMachine = new SubMachine(SubMachs.PR);
		this.configStack = new Stack<Configuration>();
		
		this.synLog = Logger.getLogger("Syntatic Logger");
		FileAppender fileApp = null;
		try {
		fileApp	= new FileAppender(new SimpleLayout(),"\\SyntaticLog.txt");
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.synLog.addAppender(fileApp);
		this.synLog.setLevel(Level.DEBUG);
		
		synLog.debug("Syntatic Object Created.");

	}


	public boolean evalToken() {
		// Gets an Arraylist with all the possible transitions for the current Submachine
		System.out.println("Current Submachine: " + currentSubMachine.getCurrentSubMachine());

		// First searches in the current submachine for a possible transition
		System.out.println("Looking for transitions with Token " + currentToken.getType());
		ArrayList<Transition> transitions = this.currentSubMachine.getSubMachTransitions();
		for (Transition trans : transitions) {
			//Checks if the transition has a null Token
			if(trans.getTokenConsumido()!= null){
				// looks for the transition
				if(trans.getEstadoAtual() == this.currentState && trans.getTokenConsumido().toString().compareTo(this.lex.getCurrentToken().getType())==0) {

					this.currentState = trans.getEstadoDestino();
					this.currentSubMachine = new SubMachine(trans.getSubMaquinaDestino());
					System.out.println("Transition found!");
					return true;
				}
			}}

		// If no transition was found, checks if the current State is a final state
		if(this.currentSubMachine.getSubMachFinalStates().contains(this.currentState)){
			// If there are stacked previous states, sets them back
			if(!configStack.empty()){
				Configuration lastConfig = this.configStack.pop();
				this.currentState = lastConfig.getNextState();
				this.currentSubMachine = new SubMachine(lastConfig.getSubMachine());
			}

			return evalToken();
		}
		else{
			System.out.println("No possible transition found for Token " + currentToken.getType() + " in submachine " + this.currentSubMachine.getCurrentSubMachine() + " from State " + this.currentState);
			return false;}
	}

	private boolean findSubMachine(){

		// If no transitions were found, then it tries Submachine calls
		System.out.println("Looking for submachine calls...");

		//There might be some points in which more then 1 submachine can be called, and all needs to be tested
		//So all of them are tried, and if the result is true in any of them, it is stored in a boolean
		boolean subMachineFound = false;
		for (Transition trans : currentSubMachine.getSubMachTransitions()) {
			// If the subMachine was found
			if(trans.getEstadoAtual() == this.currentState && trans.getTokenConsumido()==null) {
				// Store the current configuration 
				Configuration currentConfig = new Configuration(this.currentState,this.currentSubMachine.getCurrentSubMachine(), trans.getEstadoDestino());

				System.out.println("Submachine found!");

				this.configStack.push(currentConfig);

				// Sets the values for the new submachine
				this.currentSubMachine = new SubMachine(trans.getSubMaquinaDestino());
				this.currentState = 0;


				// calls the next state for this submachine, and stops if it is true
				if(evalToken()){ return true;}
				// if the new submachine does not have any trnasition for this Token, sets the state back.
				else{
					this.currentState = currentConfig.getState();
					this.currentSubMachine = new SubMachine(currentConfig.getSubMachine());
				}
			}
		}
		System.out.println("No submachines found in submachine " + this.currentSubMachine.getCurrentSubMachine() + " state " + this.currentState + " for Token " + this.currentToken.getType());
		return false;

	}


	private void setCurrentSubMachine(SubMachs currentSubMachine){

	}
	// Performs the syntatic analysis
	public boolean startsSintacticsAnalysis() {    	

		currentToken = lex.getCurrentToken();
		for(int i = 0; i < lex.getTokenSet().getlength(); i++) {


			//if it is not the End Of File token...
			if(currentToken.getType().compareTo(TokenType.EOF.toString())!=0){
				//display current token
				System.out.println("");
				System.out.println("Current Token:  " + currentToken.getType());

				// If for some reason it finds an error returns false and stops the analysis
				if(! evalToken())findSubMachine();

				// Otherwise just keep checking
				currentToken = lex.getNextToken(); 
			}
			//..otherwise checks if the stack is empty, and if the last state was final state
			else if(this.currentSubMachine.getSubMachFinalStates().contains(this.currentState)){
				System.out.println("****************************");
				System.out.println("Syntatic Analysis complete!");
				System.out.println("****************************");
				return true;
			}
		}
		//if it is not a final state, returns false.
		return false;
	}

	public Lexical getLex() {
		return lex;
	}

	public void setLex(Lexical lex) {
		this.lex = lex;
	}



}
